package com.boarsoft.boar.batch.quartz;

import static org.quartz.CronScheduleBuilder.cronSchedule;
import static org.quartz.JobBuilder.newJob;
import static org.quartz.TriggerBuilder.newTrigger;

import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

import com.boarsoft.boar.batch.service.BatchInfoService;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.quartz.impl.StdScheduler;
import org.quartz.impl.matchers.GroupMatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;

import com.boarsoft.boar.batch.bean.BatchSchedule;
import com.boarsoft.boar.batch.biz.BatchExecutionBiz;
import com.boarsoft.boar.batch.biz.BatchInfoBiz;
import com.boarsoft.boar.batch.entity.BatchInfo;
import com.boarsoft.boar.batch.service.BatchScheduler;
import com.boarsoft.common.Util;

public class BatchQuartzSchedulerImpl implements BatchScheduler {
	private final static Logger log = LoggerFactory.getLogger(BatchQuartzSchedulerImpl.class);

	/** XML中注入 */
	protected StdScheduler scheduler;
	@Autowired
	protected BatchInfoBiz infoBiz;
	@Autowired
	protected BatchExecutionBiz executionBiz;

	@PostConstruct
	public void init() throws SchedulerException {
		scheduler.start();
	}

	@PreDestroy
	public void destroy() {
		log.warn("Shutdown quartz scheduler");
		scheduler.shutdown(true);
	}

	@Override
	public List<BatchSchedule> list() throws Exception {
		List<BatchSchedule> lt = new LinkedList<BatchSchedule>();
		Set<TriggerKey> tkSet = scheduler.getTriggerKeys(GroupMatcher.anyTriggerGroup());
		for (TriggerKey tk : tkSet) {
			BatchSchedule o = new BatchSchedule();
			Trigger t = scheduler.getTrigger(tk);
			JobKey jk = t.getJobKey();
			JobDetail jd = scheduler.getJobDetail(jk);
			o.setCatalog(tk.getGroup());
			o.setCode(tk.getName());
			JobKey dk = jd.getKey();
			o.setId(dk.getName());
			o.setImpl(dk.getGroup());
			o.setJob(jd.getJobClass().getName());
			o.setCron(t.getDescription());
			o.setMemo(jd.getDescription());
			o.setNextFireTime(Util.date2str(t.getNextFireTime(), Util.STDDTMF));
			o.setEndTime(Util.date2str(t.getEndTime(), Util.STDDTF));
			o.setPreviousFireTime(Util.date2str(t.getPreviousFireTime(), Util.STDDTMF));
			o.setStartTime(Util.date2str(t.getStartTime(), Util.STDDTF));
			lt.add(o);
		}
		// 调用List的排序，返回按nextFireTime排序后的列表
		Collections.sort(lt);
		return lt;
	}

	/**
	 * 注册延时或定时启动任务
	 * 
	 * @param bi
	 *            batch info id
	 * @param delay
	 *            延时时间
	 * @param startTime
	 *            定时时间
	 * @return
	 * @throws SchedulerException
	 * @throws ClassNotFoundException
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	private Date scheduleJob(BatchInfo bi, Integer delay, Date startTime)
			throws SchedulerException, ClassNotFoundException {
		log.info("Schedule batch job {}", bi);
		if (startTime == null) {
			startTime = Util.str2date(bi.getStartTime(), Util.STDDTF);
		}
		Date stopTime = Util.str2date(bi.getStopTime(), Util.STDDTF);
		String cron = bi.getCron();
		Class jobClazz = null;
		if (Util.strIsNotEmpty(bi.getJob())) {
			jobClazz = Class.forName(bi.getJob());
		}
		//
		// JobDataMap jdm = new JobDataMap();
		// jdm.put("infoId", bi.getId());
		// jdm.put("catalog", bi.getCatalog());
		// jdm.put("code", bi.getCode());
		// jdm.put("impl", bi.getImpl());
		// jdm.put("params", bi.getParams());
		// JobDetail jd = newJob(jobClazz).withIdentity(bi.getId())//
		// .usingJobData(jdm).storeDurably().build();
		JobDetail jd = newJob(jobClazz).withIdentity(bi.getId(), bi.getImpl())
				.withDescription(bi.getMemo()).storeDurably().build();
		TriggerBuilder tb = newTrigger().withIdentity(
				bi.getId(), bi.toString()).withDescription(bi.getCron());
		if (!StringUtils.isEmpty(cron)) {
			tb.withSchedule(cronSchedule(cron));
		}

		if (startTime != null) {
			// 如果startTime小于当前系统日期,会立即执行一次，即使当前不满足cron
			if (startTime.compareTo(new Date()) > 0) {
				tb.startAt(startTime);
			} else {
				tb.startNow();
			}
		} else if (delay != null && delay != 0) {
			startTime = new Date(new Date().getTime() + 1000L * delay);
			tb.startAt(startTime);
		}

		if (stopTime != null) {
			if (stopTime.compareTo(startTime) > 0) {
				tb.endAt(stopTime);
			}
		}
		if (scheduler.checkExists(jd.getKey())) {
			scheduler.deleteJob(jd.getKey());
		}
		Date rd = scheduler.scheduleJob(jd, tb.build());
		log.info("Batch {} be scheduled, next fire time {}", bi.getId(), Util.date2str(rd, Util.STDDTMF));
		bi.setStatus(BatchSchedule.STATUS_ENABLE);
		infoBiz.save(bi);
		return rd;
	}

	@Override
	public Date schedule(BatchInfo bi, Integer delay) throws SchedulerException, ClassNotFoundException {
		return scheduleJob(bi, delay, null);
	}

	@Override
	public Date schedule(BatchInfo bi, Date startTime) throws SchedulerException, ClassNotFoundException {
		return scheduleJob(bi, null, startTime);
	}

	@Override
	public void unschedule(String id, String impl) throws SchedulerException {
		log.info("Unschedule batch schedule {}/{}", id, impl);
		scheduler.deleteJob(new JobKey(id, impl));
	}

	@Override
	public void start() throws SchedulerException {
		this.stop();
		scheduler.start();
	}

	@Override
	public void stop() {
		if (scheduler.isInStandbyMode()) {
			return;
		}
		scheduler.standby();
	}

	@Override
	public boolean isStarted() {
		return scheduler.isStarted();
	}

	@Override
	public boolean isStandByMode() {
		return scheduler.isInStandbyMode();
	}

	public StdScheduler getScheduler() {
		return scheduler;
	}

	public void setScheduler(StdScheduler scheduler) {
		this.scheduler = scheduler;
	}

	public BatchInfoBiz getInfoBiz() {
		return infoBiz;
	}

	public void setInfoBiz(BatchInfoBiz infoBiz) {
		this.infoBiz = infoBiz;
	}

	public BatchExecutionBiz getExecutionBiz() {
		return executionBiz;
	}

	public void setExecutionBiz(BatchExecutionBiz executionBiz) {
		this.executionBiz = executionBiz;
	}
}